package com.hu.hrm.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.hu.hrm.domain.CourseType;
import com.hu.hrm.feignclients.PageStaticFeignClient;
import com.hu.hrm.mapper.CourseTypeMapper;
import com.hu.hrm.service.ICourseTypeService;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.hu.hrm.constants.RedisKeyConstants;
import com.hu.hrm.feignclients.RedisFeignClient;
import com.hu.hrm.util.AjaxResult;
import com.hu.hrm.vo.CrumbsVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.*;

/**
 * <p>
 * 课程目录 服务实现类
 * </p>
 *
 * @author woo
 * @since 2020-02-18
 */
@Service
public class CourseTypeServiceImpl extends ServiceImpl<CourseTypeMapper, CourseType> implements ICourseTypeService {

    @Autowired
    private RedisFeignClient redisFeignClient;

    @Autowired
    private PageStaticFeignClient pageStaticFeignClient ;

    /**
     * redis缓存更新，复写持久化的方法，主要目的让他们（事务性的操作）更新到redis
     * 恰巧他们改了数据，也需要更新页面静态化
     */
    @Override
    public boolean insert(CourseType entity) {
        boolean insertStatus = super.insert(entity);
        //因为执行了事务性的操作所以同步一下Redis缓存
        resetRedisForCourseType();
        //触发页面静态化
        triggerPageStatic();
        return insertStatus;
    }
    @Override
    public boolean deleteById(Serializable id) {
        boolean deleteByIdStatus = super.deleteById(id);
        //因为执行了事务性的操作所以同步一下Redis缓存
        resetRedisForCourseType();
        //触发页面静态化
        triggerPageStatic();
        return deleteByIdStatus;
    }
    @Override
    public boolean updateById(CourseType entity) {
        boolean updateByIdStatus = super.updateById(entity);
        //因为执行了事务性的操作所以同步一下Redis缓存
        resetRedisForCourseType();
        //触发页面静态化
        triggerPageStatic();
        return updateByIdStatus;
    }


    //触发课程首页页面静态化（准备自动触发，当页面信息发生改变，就调用 达到目的， 恰巧就是上面三个方法）
    //做课程首页页面静态化的方法
    public void triggerPageStatic(){
        //1.首先准备页面需要的数据库的数据存储到Redis（利用Redis的优点，让数据库中 需要的课程数据 利用更快）
        //这里就可以直接调用下面的方法： 得到数据
        List<CourseType> courseTypes = this.treeData();

/* 考虑到生产模板的地方 使用的是map装数据，所以修改类型的同时，还要注意先顺序（数据有了再放Redis，所以这里一下去了）
        //设置Key 存进Redis  （由于在redisFeignClient中set方法 设的是字符串，所以需要转数据类型）
        AjaxResult ajaxResult = redisFeignClient.set("course_types_page_static", JSON.toJSONString(courseTypes));
        //考虑到set可能保存失败，所以通常要拿一下返回结果，判断
        if(!ajaxResult.isSuccess()){
            throw new RuntimeException("页面静态化数据准备失败（读入Redis可能失败，请检查）"+ajaxResult.getMessage());
        }*/

        //2.准备参数：因为我们需要数据的redis的key和t_page的name （key我们知道了）
        String pageName = "home";

        //封装数据的map    模板的属性名是courseTypes  需要的是List集合 村的是数据，恰巧就是treeData();即courseTypes
        Map<String,Object> model = new HashMap<>();
        model.put("courseTypes",courseTypes);


        //设置Key 存进Redis  （由于在redisFeignClient中set方法 设的是字符串，所以需要转数据类型）
        AjaxResult ajaxResult = redisFeignClient.set(RedisKeyConstants.COURSE_TYPES_PAGE_STATIC, JSON.toJSONString(model));
        //考虑到set可能保存失败，所以通常要拿一下返回结果，判断
        if(!ajaxResult.isSuccess()){
            throw new RuntimeException("页面静态化数据准备失败（读入Redis可能失败，请检查）"+ajaxResult.getMessage());
        }

        //参数准备好了，就可以完善页面静态化微服务和它的Feign了。然后通过Feign去调用微服务即可
        //注意课程服务依赖静态化微服务的Feign
        //3.参数、静态化微服务接口及Feign都准备好了，就可以通过Feign调用页面静态化服务,触发页面静态化
        AjaxResult ajaxResultPageStatic = pageStaticFeignClient.pageStatic(pageName, RedisKeyConstants.COURSE_TYPES_PAGE_STATIC);
        if(!ajaxResultPageStatic.isSuccess()){
            throw new RuntimeException("页面静态化失败（调用静态化微服务pageStaticFeignClient可能失败，请检查）："+ajaxResultPageStatic.getMessage());
        }

    }

    //重置（重查）Redis中的课程分类数据
    private  List<CourseType>  resetRedisForCourseType(){
        List<CourseType> courseTypes = baseMapper.selectList(null);
        //查之后同步一份到Redis
        redisFeignClient.set(RedisKeyConstants.COURSE_TYPE,JSON.toJSONString(courseTypes));
        return courseTypes;
    }

    /**
    * 一.返回的CourseType 有什么问题？
    * （当然返回个前端的json什么的都解决了，现在主要是找返回什么内容）
    *  所以分析可知： 我们这个接口需要的数据大概接口内容（前端可以找到蛛丝马迹）：
    *
    *  我们需要CourseType中添加一个字段来帮助，所以实体类中：
    *
    *  二.返回的List的size是多长呢？
    *
    *  三.然后怎么样来封装数据呢？
    *       （把所有课程分类查询出来，然后再内存中组装）
    *  根据数据
    *
    **/
    @Override
    public List<CourseType> treeData() {
        //准备List （所以课程类型）
        List<CourseType> courseTypes = null;
        //获取Redis
        AjaxResult ajaxResult = redisFeignClient.get(RedisKeyConstants.COURSE_TYPE);//准备了托底，处理未开启Redis情况
        //而托底中返回的是AjaxResult  false 以便让他在下面的逻辑中进入  重置
        //当然在托底中也可以让它去查  但是没有必要了 ，有的东西 何必再写

        //判断Redis中是否有内容
        if (ajaxResult.isSuccess() && null != ajaxResult.getResultObj()){
            //有的话 返回
            String jsonFromRedis = ajaxResult.getResultObj().toString();
            //存在redis中的数据 ，要不要有层级结构 :放的是没有处理过的list？
            //转换数据类型
            courseTypes = JSON.parseArray(jsonFromRedis, CourseType.class);
        }else {
            //如果没有  重置
            courseTypes = resetRedisForCourseType();
        }
        //1.查询所有的课程类型
//        courseTypes = baseMapper.selectList(null);  //解释注释了的原因：提取出去了

        //2.先过滤一级分类
        //用来封装一级分类（当然每个一级分类的children中有其子分类）
        ArrayList<CourseType> primaryCourseType = new ArrayList<>();

        //（怎样分类呢？观察数据库，一级分类pid为0，找出来装，然后每个子分类找出来装）
        //找到一级分类（最大的父课程分类）
        for (CourseType courseType : courseTypes) {
            //循环判断每一个课程类型，mysql中可知Pid为0的是一级课程分类
            if (courseType.getPid().longValue() == 0){
                primaryCourseType.add(courseType);
            }else {
                //思考怎么装这个子分类呢？怎么拿？
                //当不是一级的时候，就要找到他的父分类，自己去找自己的父分类，装起来（和之前做菜单是一个道理，很好的方案）
                //所以 定义一个当前分类，用自己的pid去比较
                CourseType currentPrimaryCourseType = null;
                for (CourseType pcourseType : courseTypes) {
                    //再循环courseTypes 判断id和pid
                    if (courseType.getPid().longValue() == pcourseType.getId().longValue()){
                        currentPrimaryCourseType = pcourseType;
                        break;
                    }
                }
                if (currentPrimaryCourseType != null){
                    //如果比较后找到了自己的父分类 ，就把当前分类加入到父分类的chidren中
                    currentPrimaryCourseType.getChildren().add(courseType);
                }
            }
        }
        return primaryCourseType;
    }

    @Override
    public List<CrumbsVO> crumbs(Long id) {
        //1.根据课程分类查询所有父分类

        //先查询到类型
        CourseType currentCourseType = baseMapper.selectById(id);
        //然后获取父级分类（数据库中是路径）
        String path = currentCourseType.getPath();

        //由于数据不规范（有些前面有点有些后面有点，都先去掉）
        //去掉前面的点.
        path = path.startsWith(".")?path.substring(1):path;
        //去掉后面的点  0开启，截取长度-1
        path = path.endsWith(".")?path.substring(0,path.length()-1):path;

        //然后就可以：获取所有的父级分类  split要的是一个正则表达式 点在这个表达式中有特殊含义，所以转义 然后\也需要转义 所以是两个\
        String[] types = path.split("\\.");

        //最终返回的结果
        List<CrumbsVO> result = new ArrayList<>();

        for(String type : types ){

            //准备组装
            CrumbsVO vo = new CrumbsVO();

            //查
            Long typeId = Long.valueOf(type);
            CourseType courseType = baseMapper.selectById(typeId);

            //组装（差一个就可以组装一个撒）
            //2.分类进行组装 ，一个分类一个CrumbsVO 节点  即一个代表 CrumbsVO.ownerProductType
            vo.setOwnerProductType(courseType);

            //3.然后为每个分类查询其兄弟姐妹分类，设置到CrumbsVO.otherProductTypes
            //得到节点的父节点
            Long pid = courseType.getPid();
            List<CourseType> otherCoutseTypes = baseMapper.selectByPid(pid);

            //4.删除自己(迭代)
            Iterator<CourseType> iterator = otherCoutseTypes.iterator();
            while (iterator.hasNext()){
                if(iterator.next().getId().equals(courseType.getId())){
                    iterator.remove();
                    break;
                }
            }

            //装入
            vo.setOtherProductTypes(otherCoutseTypes);

            result.add(vo);
        }

        return result;
    }
}
